package com.dingwen.treasure.kettle.base.aspect;

import cn.hutool.core.util.StrUtil;
import com.dingwen.treasure.kettle.base.annotation.ReSubmit;
import com.dingwen.treasure.kettle.base.redis.RedisService;
import com.dingwen.treasure.kettle.enums.RequestParamEnum;
import com.dingwen.treasure.kettle.exception.ReSubmitException;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.springframework.http.HttpStatus;
import org.springframework.stereotype.Component;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;

import javax.servlet.http.HttpServletRequest;


/**
 * 防止重复提交切面，作用注解{@link ReSubmit} <br>
 * <ol>
 *     <li> redis的Key删除策略，过期即删除</li>
 * </ol>
 *
 * @author dingwen
 * @date 2022/04/04
 */
@Component
@Aspect
@Slf4j
@RequiredArgsConstructor
public class ReSubmitAspect {

    /**
     * redis 服务
     */
    private final RedisService redisService;


    @Around("@annotation(reSubmit)")
    public Object doAround(ProceedingJoinPoint joinPoint, ReSubmit reSubmit) throws Throwable {

        // 1. 生成key
        String redisKey = generateRedisKey(reSubmit, joinPoint.getSourceLocation().getWithinType().getName(), joinPoint.getSignature().getName());

        // 2. 获取key过期时间
        long expire = redisService.getExpire(redisKey);


        if (expire > 0) {
            String message = StrUtil.format("用户：[{}],频繁的操作，[{}]秒后再试,消息:[{}]", "userId", expire, reSubmit.message());
            log.info(message);
            throw new ReSubmitException(HttpStatus.TOO_MANY_REQUESTS.value(), message);
        }

        // 存在则不设置返回false,不存在设置返回true
        Boolean ifAbsent = redisService.setIfAbsent(
                redisKey,
                "resubmit",
                reSubmit.time(),
                reSubmit.timeUnit()
        );

        if (Boolean.FALSE.equals(ifAbsent)) {
            String message = StrUtil.format("用户：[{}]频繁操作，请稍后再试", "userId");
            log.info(message);
            throw new ReSubmitException(HttpStatus.TOO_MANY_REQUESTS.value(), message);
        }
        Object result = joinPoint.proceed();

        // 删除key处理
        isDeleteKeyHandler(reSubmit.isDeleteKey(), redisKey);

        return result;
    }

    /**
     * 生成redis key
     *
     * @param annotation 注解元信息
     * @param methodName 方法名称
     * @param className  类名称
     * @return {@link String}
     */
    private String generateRedisKey(ReSubmit annotation, String methodName, String className) {

        String key = annotation.key();
        String param = annotation.param();

        StringBuilder stringBuilder = new StringBuilder();

        // 获取用户Id
        Long userId = 1L;

        // 用户Id存在，且配置开启userId拼接
        if (annotation.isAppendUserId()) {
            stringBuilder.append(":").append(userId);
        }


        if (StrUtil.isNotBlank(key)) {
            stringBuilder.append(key).append(":");
        }
        stringBuilder
                .append(methodName)
                .append(":")
                .append(className);


        if (StrUtil.isBlank(param)) {
            return stringBuilder.toString();
        }

        // 获取请求字符串
        String paramString = getParamString(annotation.paramType(), param);

        if (StrUtil.isNotBlank(paramString)) {
            stringBuilder.append(":").append(paramString);
        }
        return stringBuilder.toString();
    }

    /**
     * 获取参数字符串
     *
     * @param requestParamEnum 请求参数枚举
     * @param param            参数
     * @return {@link String}
     */
    private String getParamString(RequestParamEnum requestParamEnum, String param) {
        // 获取request,值得学习
        ServletRequestAttributes servletRequestAttributes = (ServletRequestAttributes) RequestContextHolder.currentRequestAttributes();
        //获取请求
        HttpServletRequest request = servletRequestAttributes.getRequest();

        switch (requestParamEnum) {
            case BODY:
                return RequestParamEnum.BODY.getCode();
            case FORM:
                return request.getParameter(param);
            case HEADER:
                return request.getHeader(param);
            default:
                return null;
        }
    }

    /**
     * 是删除key处理程序
     *
     * @param isDeleteKey 是删除键
     * @param key         钥匙
     */
    private void isDeleteKeyHandler(boolean isDeleteKey, String key) {
        if (isDeleteKey) {
            redisService.deleteObject(key);
        }
    }
}
